﻿// See https://aka.ms/new-console-template for more information

//使用emit
using System.Reflection;
using System.Reflection.Emit;

//一般的 使用 refection emit会是这样的步骤
//1.创建一个新的程序集（辰戌吉是动态的存在于内存中的或把他们保存到磁盘上）
//2.在程序集内部，创造一个模块
//3.在模块内部，创建一个类型
//4.给类型添加属性和方法
//5.产生属性和方法内部的代码
//确切的讲，当你使用Reflection.Emit类产生代码时，以上描述是你实际上要遵循的过程

AssemblyName aName = new AssemblyName("DynamicAssemblyExample");//定义程序集名称

AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(aName, AssemblyBuilderAccess.RunAndCollect);//程序集建造者 AssemblyBuilderAccess.RunAndCollect表示使用完后会自动回收

//添加模块
ModuleBuilder mb = ab.DefineDynamicModule("MyModule");

TypeBuilder tb = mb.DefineType("MyDynamicType", TypeAttributes.Public);//基于moudel，配置要生成的类

FieldBuilder fbNumber = tb.DefineField("m_number", typeof(int), FieldAttributes.Private);//添加int类型的私有字段

//定义一个接受整数参数的构造函数
//存储在私人区域
Type[] parameterTypes = { typeof(int) };
ConstructorBuilder ctor1 = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, parameterTypes);

ILGenerator ctor1IL = ctor1.GetILGenerator();

//接收一个int类型参数的构造函数
//调用base之前，将其推到堆栈上
//通过类构造函数，指定默认构造函数
//通过传递类型 （Type.EmptyTypes）到构造函数

ctor1IL.Emit(OpCodes.Ldarg_0);//将索引为0的参数加载到计算机堆栈上
ctor1IL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));//调用由传递的方法说明符指示的方法

//在推送参数之前，先将实例推送到堆栈上
//将被分配给私有字段meu编号

ctor1IL.Emit(OpCodes.Ldarg_0);//将索引为0的参数加载到计算机堆栈上
ctor1IL.Emit(OpCodes.Ldarg_1);//将索引为1的参数加载到计算机堆栈上
ctor1IL.Emit(OpCodes.Stfld, fbNumber);//用新值替换在对象引用或指针的字段中存储的值。
ctor1IL.Emit(OpCodes.Ret);//从当前方法返回，并将返回值（如果存在）从调用方的计算堆栈推送到被调用方的计算堆栈上。

//定义无参数构造函数
//定义提供默认值的默认构造函数
//私人领域。对于参数类型，传递空
//类型数组或传递null
ConstructorBuilder ctor0 = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
ILGenerator ctor0Il = ctor0.GetILGenerator();

ctor0Il.Emit(OpCodes.Ldarg_0);
ctor0Il.Emit(OpCodes.Ldc_I4_S, 42);//将提供的 int8 值作为 int32 推送到计算堆栈上（短格式）。
ctor0Il.Emit(OpCodes.Call, ctor1);
ctor0Il.Emit(OpCodes.Ret);

//添加一个int类型名称为Number的属性
PropertyBuilder pbNumber = tb.DefineProperty("Number", PropertyAttributes.HasDefault, typeof(int), null);

//属性get和set方法需要特殊的属性集
MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

//为Number定义“get”方法,方法返回一个整数，没有参数（注意null可以是代替类型使用，空类型）
MethodBuilder mbNumberGetAccessor = tb.DefineMethod("get_Number", getSetAttr, typeof(int), Type.EmptyTypes);

ILGenerator numbetGetIL = mbNumberGetAccessor.GetILGenerator();

//对于实例属性，参数0是实例，加载实例，然后加载private字段并返回堆栈上的字段值。
numbetGetIL.Emit(OpCodes.Ldarg_0);
numbetGetIL.Emit(OpCodes.Ldfld, fbNumber);
numbetGetIL.Emit(OpCodes.Ret);

//为Number属性定义“set”方法，该方法没有返回值，接受一个int类型的参数
MethodBuilder mbBunmberSetAccessor = tb.DefineMethod("set_Number", getSetAttr, null, new Type[] { typeof(int) });
ILGenerator numberSetIL = mbBunmberSetAccessor.GetILGenerator();
//加载实例和参数，然后存储
numberSetIL.Emit(OpCodes.Ldarg_0);
numberSetIL.Emit(OpCodes.Ldarg_1);
numberSetIL.Emit(OpCodes.Stfld, fbNumber);
numberSetIL.Emit(OpCodes.Ret);

//最后，将get和set访问器方法映射到属性生成器，该属性现在已经完成
pbNumber.SetGetMethod(mbNumberGetAccessor);
pbNumber.SetSetMethod(mbBunmberSetAccessor);

//定义一个接受整形参数并返回该整数与专用字段meu数的乘积，参数类型的数组是动态创建的
MethodBuilder meth = tb.DefineMethod("MyMethod", MethodAttributes.Public, typeof(int), new Type[] { typeof(int) });
ILGenerator methIL = meth.GetILGenerator();

//检索private instance字段，将实例加载到内存上，相乘返回结果
methIL.Emit(OpCodes.Ldarg_0);
methIL.Emit(OpCodes.Ldfld, fbNumber);
methIL.Emit(OpCodes.Ldarg_1);
methIL.Emit(OpCodes.Mul);
methIL.Emit(OpCodes.Ret);

//完成类型
Type t = tb.CreateType();

MethodInfo mi = t.GetMethod("MyMethod");
PropertyInfo pi = t.GetProperty("Number");

//使用默认值创建MyDynamicType的实例
object o1 = Activator.CreateInstance(t);
Console.WriteLine($"o1.Number :{pi.GetValue(o1, null)}");
pi.SetValue(o1, 10000, null);
Console.WriteLine($"o1.Number :{pi.GetValue(o1, null)}");

Console.WriteLine($"o1.MyMethod() : {mi.Invoke(o1, new object[] { 20 })}");
Console.WriteLine($"o1.MyMethod() : {mi.Invoke(o1, new object[] { 40 })}");
pi.SetValue(o1, 5, null);
Console.WriteLine($"o1.MyMethod() : {mi.Invoke(o1, new object[] { 8 })}");



//AssemblyName在.net8中不支持sava
//根据.net更新计划，在.net9中才重新支持save

Console.WriteLine("Hello, World!");